Skip to main content

Dispensables

Semua jenis Dispensables, penjelasan, contoh kode nyata, dan cara refactoringnya

Dispensable adalah sesuatu yang tidak berguna dan tidak diperlukan. Menghapusnya justru membuat code lebih bersih, efisien, dan mudah dipahami.

Sumber: Refactoring Guru


Comments

Sebuah method dipenuhi komentar penjelas — biasanya karena code-nya buruk, bukan karena code-nya kompleks.

Masalah

Komentar berlebihan digunakan untuk menutupi code yang berantakan. Komentar juga bisa menjadi "kebohongan" ketika code diupdate tapi komentarnya tidak.

"Komentar terbaik adalah nama method atau class yang bagus."

Explorer
comment
MenuPrinter.java
Main.java
comment/MenuPrinter.java
package comment;

import java.util.Scanner;

public class MenuPrinter {
public int printMenuAndGetInput(){
printHeader();
printMenu();
return getInput();
// Nama method sudah menjelaskan dirinya sendiri
// Tidak perlu komentar // Cetak header, // Cetak menu, dll.
}

private int getInput() {
int input = 0;
Scanner sc = new Scanner(System.in);
do {
System.out.println("Input [1-4]: ");
input = sc.nextInt();
sc.nextLine();
} while(input < 1 || input > 4);

sc.close();
return input;
}

private void printMenu() {
System.out.println("1. Foo");
System.out.println("2. Bar");
System.out.println("3. Baz");
System.out.println("4. Exit");
}

private void printHeader() {
System.out.println("====");
System.out.println("Menu");
System.out.println("====");
}
}

Solusi

Pecah method panjang menjadi method-method kecil dengan nama yang deskriptif. Nama method menggantikan komentar.

Sebelum — satu method panjang dengan komentar penjelas
public class MenuPrinter {
public int printMenuAndGetInput() {
// Cetak header
System.out.println("====");
System.out.println("Menu");
System.out.println("====");

// Cetak pilihan menu
System.out.println("1. Foo");
System.out.println("2. Bar");
System.out.println("3. Baz");
System.out.println("4. Exit");

// Validasi dan ambil input user
int input = 0;
do { input = sc.nextInt(); } while (input < 1 || input > 4);
return input;
}
}
Sesudah — method-method kecil bernama deskriptif
public class MenuPrinter {
public int printMenuAndGetInput() {
printHeader(); // nama sudah jelas
printMenu(); // tidak perlu komentar
return getInput();
}

private void printHeader() { /* logika header */ }
private void printMenu() { /* logika menu */ }
private int getInput() { /* logika input */ }
}

Perbandingan

FiturSebelumSesudah
KeterbacaanHarus baca komentar dan code sekaligusCode terbaca seperti kalimat alami
PengujianTidak bisa test komentar; bisa berbohongBisa tulis unit test untuk tiap method kecil
PemeliharaanKomentar sering tidak diupdate saat code berubahSaat logika berubah, nama method ikut diupdate
ReusabilitasLogika terkurung dalam satu method besarMethod-method kecil bisa dipanggil dari bagian lain

Duplicate Code

Dua atau lebih fragment code yang hampir identik — biasanya hasil copy-paste.

Masalah

Code duplikat melanggar prinsip DRY (Don't Repeat Yourself). Ada bug di logika itu? Harus diperbaiki di semua tempat. Kalau lupa satu tempat — inconsistent.

Explorer
duplicate_code
Foo.java
Main.java
duplicate_code/Foo.java
package duplicate_code;

public class Foo {
// Setelah refactoring: methodHaha dan methodHihi
// tidak lagi duplikat — keduanya delegasi ke printFormatedText
protected void methodHaha() {
printFormatedText("Haha");
}

protected void methodHihi() {
printFormatedText("Hihi");
}

protected void methodHiHa() {
System.out.println("HiHiHiHa");
}

// Satu helper — logika format ada di sini saja
private void printFormatedText(String text) {
clearScreen();
printSeperator();
System.out.println(text);
printSeperator();
}

private void printSeperator() {
for (int i = 0; i < 3; i++) {
System.out.print("=");
}
System.out.println("");
}

private void clearScreen() {
for (int i = 0; i < 10; i++) {
System.out.println("");
}
}
}

Solusi

Extract logika duplikat ke satu method helper. Method-method lain cukup memanggil helper tersebut.

Sebelum — metodHaha dan methodHihi identik
public class Foo {
protected void methodHaha() {
// Blok code IDENTIK dengan methodHihi — hanya teks beda!
clearScreen();
for (int i = 0; i < 3; i++) System.out.print("=");
System.out.println("");
System.out.println("Haha");
for (int i = 0; i < 3; i++) System.out.print("=");
System.out.println("");
}

protected void methodHihi() {
// Copy-paste dari methodHaha
clearScreen();
for (int i = 0; i < 3; i++) System.out.print("=");
System.out.println("");
System.out.println("Hihi");
for (int i = 0; i < 3; i++) System.out.print("=");
System.out.println("");
}
}
Sesudah — logika diextract ke printFormatedText
public class Foo {
protected void methodHaha() {
printFormatedText("Haha"); // bersih
}

protected void methodHihi() {
printFormatedText("Hihi"); // bersih
}

private void printFormatedText(String text) {
clearScreen();
printSeperator();
System.out.println(text);
printSeperator();
}
}

Perbandingan

FiturSebelumSesudah
KeterbacaanHarus baca logika sama berkali-kaliNama printFormatedText langsung menjelaskan
PengujianHarus tulis test terpisah untuk setiap salinanTulis satu test untuk helper, percayakan ke semua pemanggil
PemeliharaanFormat separator berubah = cari semua salinannyaUbah di satu method, seluruh aplikasi terupdate
ReusabilitasLogika terkurung sebagai raw code di method spesifikHelper bisa dipanggil dari bagian lain class

Lazy Class

Class yang hanya melakukan sedikit hal dan hampir tidak berguna — biasanya sisa refactoring yang belum selesai.

Masalah

PriceValidator hanya punya satu static method dengan satu baris logika. Keberadaannya menambah kompleksitas tanpa manfaat nyata.

Explorer
lazy_class
Price.java
PriceValidator.java
lazy_class/PriceValidator.java
package lazy_class;

// Gini doang? Buat apa?
// Class ini terlalu kecil untuk berdiri sendiri!
public class PriceValidator {
public static boolean validate(int value){
return value >= 0;
}
}

Solusi

Pindahkan logika ke class utama dan hapus class lazy tersebut.

Sebelum — PriceValidator terlalu kecil untuk berdiri sendiri
// Class lazy yang tidak perlu
public class PriceValidator {
public static boolean validate(int value) {
return value >= 0; // satu baris saja
}
}

public class Price {
public Price(int value) throws Exception {
if (!PriceValidator.validate(value)) { // kenapa harus ke class lain?
throw new Exception("price not valid");
}
}
}
Sesudah — validasi langsung masuk ke Price
// PriceValidator sudah dihapus!
public class Price {
private int value;

public Price(int value) throws Exception {
if (!isPriceValid(value)) { // validasi di sini
throw new Exception("price not valid");
}
this.value = value;
}

public boolean isPriceValid(int value) {
return value >= 0;
}
}

Perbandingan

FiturSebelumSesudah
KeterbacaanHarus loncat ke file lain hanya untuk satu baris validasiLogika ada tepat di mana kamu mencarinya
PengujianHarus setup objek ekstra yang tidak perluTest langsung pada class utama
PemeliharaanLebih banyak file untuk dilacakLebih sedikit file, lebih sedikit "bagian bergerak"
ReusabilitasClass terlalu kecil sehingga lebih mudah ditulis ulangLogika menjadi bagian dari objek utama

Data Class

Class yang hanya berisi fields, getter, dan setter — tanpa behavior apapun.

Masalah

Data class murni memaksa class-class lain memegang logika yang seharusnya ada di dalam data tersebut. Ini melanggar encapsulation.

Explorer
data_class
User.java
data_class/User.java
package data_class;

public class User {
private String firstName;
private String lastName;

private String address;
private String phone;

// Ini yang membedakan dari Data Class murni:
// class punya BEHAVIOR, bukan hanya getter/setter
public void login() {
// logika login ada di sini
}

public void logout() {
// logika logout ada di sini
}

public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}

Solusi

Pindahkan logika yang berkaitan dengan data ke dalam class data itu sendiri.

Sebelum — Data Class murni, hanya getter/setter
// Data Class: tidak ada behavior sama sekali
public class User {
private String firstName;
private String lastName;

public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
// ... hanya getter dan setter ...
}

// Class lain terpaksa pegang logika User
public class UserLoginService {
public void login(User user, String password) {
// Logika login ada di sini, padahal harusnya milik User!
}
}
Sesudah — class punya behavior yang relevan
public class User {
private String firstName;
private String lastName;

// Behavior milik User ada di sini
public void login() { /* logika login */ }
public void logout() { /* logika logout */ }

public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
}

Perbandingan

FiturSebelumSesudah
KeterbacaanHarus cari class lain untuk lihat bagaimana data digunakanKemampuan objek terlihat dari method-nya
PengujianHarus test class klien untuk verifikasi logika dataTest langsung pada objeknya sendiri
PemeliharaanJika struktur data berubah, setiap class klien bisa rusakPerubahan internal tersembunyi
ReusabilitasBagian lain harus tulis ulang logika untuk pakai dataTinggal panggil user.login()

Dead Code

Variable, parameter, field, method, atau class yang sudah tidak pernah digunakan lagi.

Masalah

Code yang tidak terpakai — biasanya sisa perubahan requirement — membingungkan developer baru yang menghabiskan waktu mencoba memahami code yang tidak melakukan apa-apa.

Explorer
dead_code
PriceCalculator.java
dead_code/PriceCalculator.java
package dead_code;

public class PriceCalculator {
public double calculate(double price, boolean isDiscount) {
double discountPrice = 0;

// DEAD CODE: logika lama yang sudah tidak dipakai
// jika harga kurang dari 10000, diskon 10%
// selain itu, diskon 20%
// if(isDiscount){
// if(price < 10000){
// discountPrice = price * 0.1;
// } else {
// discountPrice = price * 0.2;
// }
// }

// requirement berubah
// diskon diketok rata 15%
if (isDiscount)
discountPrice = price * 0.15;

return price - discountPrice;
}
}

Solusi

Hapus code yang tidak dipakai. Kalau perlu referensi di masa depan, gunakan version control (git) — bukan komentar.

Sebelum — code lama ter-comment out (dead code)
public class PriceCalculator {
public double calculate(double price, boolean isDiscount) {
double discountPrice = 0;

// DEAD CODE yang membingungkan!
// if(isDiscount){
// if(price < 10000){
// discountPrice = price * 0.1;
// } else {
// discountPrice = price * 0.2;
// }
// }

// requirement berubah, diskon rata 15%
if (isDiscount) discountPrice = price * 0.15;
return price - discountPrice;
}
}
Sesudah — hanya logika aktif yang tersisa
public class PriceCalculator {
public double calculate(double price, boolean isDiscount) {
double discountPrice = 0;

if (isDiscount)
discountPrice = price * 0.15;

return price - discountPrice;
}
}

Perbandingan

FiturSebelumSesudah
KeterbacaanDeveloper buang waktu memahami code yang tidak melakukan apa-apaSetiap baris punya tujuan aktif yang jelas
PengujianBisa tidak sengaja test fitur "hantu"Test coverage fokus pada code yang benar-benar dijalankan
PemeliharaanHarus refactor dead code saat migrasi tanpa alasanLebih sedikit code = lebih sedikit yang perlu diurus
ReusabilitasDead code tidak memberikan nilai apapunHanya logika berkualitas tinggi yang tersedia

Speculative Generality

Class, method, atau abstraksi yang dibuat "kalau-kalau dibutuhkan di masa depan" — tapi fiturnya tidak pernah benar-benar diimplementasikan.

Masalah

Hierarki abstrak Currency → IDR, USD dibuat untuk mengantisipasi multi-currency, padahal saat ini hanya dipakai untuk menampilkan kode string. Over-engineering untuk kebutuhan yang tidak nyata.

Explorer
speculative_generality
Currency.java
IDR.java
USD.java
Price.java
speculative_generality/Currency.java
package speculative_generality;

// Abstract class yang tidak diperlukan saat ini
// Dibuat hanya untuk mengantisipasi kemungkinan di masa depan
public abstract class Currency {
public abstract String getCode();
}

Solusi

Hapus abstraksi yang tidak perlu. Ketika kebutuhan nyata datang, tambah abstraksi saat itu berdasarkan kebutuhan nyata — bukan spekulasi.

Sebelum — hierarki abstrak untuk kebutuhan spekulatif
// 4 file hanya untuk menyimpan kode mata uang!
abstract class Currency { abstract String getCode(); }
class IDR extends Currency { String getCode() { return "IDR"; } }
class USD extends Currency { String getCode() { return "USD"; } }

class Price {
Currency currency; // harus buat IDR/USD object hanya untuk string
Price(int value, Currency currency) { ... }
}
Sesudah — String sudah cukup untuk saat ini
// 1 file, sederhana
class Price {
private int value;
private String currencyCode; // "IDR" atau "USD" — String sudah cukup!

Price(int value, String currencyCode) { ... }
}
// Currency.java, IDR.java, USD.java sudah dihapus!

Perbandingan

FiturSebelumSesudah
KeterbacaanHarus navigasi berlapis-lapis hanya untuk menemukan code nyataCode langsung menunjukkan apa yang dilakukan sekarang
PengujianHarus mock interface generik kompleks untuk test sederhanaTest langsung pada class konkret
PemeliharaanTerpaksa maintain code yang tidak melayani kebutuhan saat iniLebih sedikit code = lebih mudah diubah saat kebutuhan nyata tiba
ReusabilitasAbstraksi "generik" sering terlalu kabur untuk benar-benar digunakanCode spesifik lebih mudah diekstrak jadi abstraksi nyata nanti